昨天先把非同步的基本觀念(callback → promise → async/await)走過一遍。
今天升級到「同時做很多事」以及「錯誤處理」:Promise.all、Promise.race,還會自己做一個 延遲工具 來模擬 API 速度與錯誤。
為了練習方便,我先做兩個小工具函式:
// 延遲工具:回傳一個在 ms 毫秒後 resolve 的 Promise
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// 模擬 API:過 ms 毫秒後成功或失敗
function mockFetch(name, ms, shouldFail = false) {
return new Promise(async (resolve, reject) => {
await delay(ms);
if (shouldFail) {
reject(new Error(`${name} 失敗(延遲 ${ms}ms)`));
} else {
resolve({ name, ms, data: `這是 ${name} 的資料` });
}
});
}
情境:同時打三個 API,要全部都成功才算成功;其中一個失敗就整組失敗。
async function runAll() {
try {
console.time("ALL");
const result = await Promise.all([
mockFetch("users", 800),
mockFetch("posts", 1200),
mockFetch("comments", 600),
]);
console.timeEnd("ALL");
// result 是依照陣列順序回來(不是依完成時間)
console.log("ALL 成功,結果:", result);
} catch (err) {
console.error("ALL 發生錯誤:", err.message);
}
}
runAll();
重點觀察:
Promise.all
只有在三個都成功才會 resolve。catch
。情境:我只想要最快回來的結果(例如多個鏡像站、或超時改用備援)。
async function runRace() {
try {
const fastest = await Promise.race([
mockFetch("A 伺服器", 900),
mockFetch("B 伺服器", 500),
mockFetch("C 伺服器", 700),
]);
console.log("RACE 最快回來:", fastest);
} catch (err) {
console.error("RACE 發生錯誤:", err.message);
}
}
runRace();
重點觀察:
race
就會結束。catch
。把錯誤處理放在 async function
裡,用 try/catch
包起來就好讀很多:
async function fetchAllWithTryCatch() {
try {
const [users, posts, comments] = await Promise.all([
mockFetch("users", 300),
mockFetch("posts", 600),
mockFetch("comments", 900, /* shouldFail */ false),
]);
console.log("users:", users.data);
console.log("posts:", posts.data);
console.log("comments:", comments.data);
} catch (error) {
console.error("抓資料失敗:", error.message);
}
}
fetchAllWithTryCatch();
需求:同時抓三個 API,但「其中一個」失敗了,我想要:
try/catch
抓到錯誤訊息。Promise.allSettled
,取得每個請求的結果(成功或失敗),自己決定要怎麼處理。async function allButOneFails() {
try {
const data = await Promise.all([
mockFetch("users", 500),
mockFetch("posts", 700, /* shouldFail */ true), // 故意失敗
mockFetch("comments", 400),
]);
console.log("這行不會被執行(因為上面會丟錯)", data);
} catch (e) {
console.error("ALL:其中一個失敗 → 直接進 catch:", e.message);
}
}
allButOneFails();
雖然今天主題不是它,但在「有失敗也想看成功的結果」時非常好用。
async function allSettledExample() {
const results = await Promise.allSettled([
mockFetch("users", 500),
mockFetch("posts", 700, true), // 失敗
mockFetch("comments", 400),
]);
const successes = results
.filter(r => r.status === "fulfilled")
.map(r => r.value);
const failures = results
.filter(r => r.status === "rejected")
.map(r => r.reason.message);
console.log("成功清單:", successes);
console.log("失敗清單:", failures);
}
allSettledExample();
我用前面的 mockFetch
來模擬三個 API:使用者、文章、留言。
第一段:全部成功 → 輸出整合結果。
第二段:其中一個失敗 → 用 try/catch
抓錯,或改用 allSettled
部分採納。
// ✅ 版本 A:全成功(Promise.all)
async function demoAllSuccess() {
try {
const [users, posts, comments] = await Promise.all([
mockFetch("users", 800),
mockFetch("posts", 1200),
mockFetch("comments", 600),
]);
console.log("成功整合:", {
users: users.data,
posts: posts.data,
comments: comments.data,
});
} catch (e) {
console.error("不會觸發(此版都成功)", e.message);
}
}
// ⚠️ 版本 B:其中一個失敗(Promise.all + try/catch)
async function demoAllWithFailure() {
try {
const [users, posts, comments] = await Promise.all([
mockFetch("users", 800),
mockFetch("posts", 1200, true), // 故意失敗
mockFetch("comments", 600),
]);
console.log("這行不會到達:", users, posts, comments);
} catch (e) {
console.error("有一個失敗 → 直接進 catch:", e.message);
}
}
// ✅ 版本 C:其中一個失敗,但我想看「成功的那些」(allSettled)
async function demoAllSettled() {
const results = await Promise.allSettled([
mockFetch("users", 800),
mockFetch("posts", 1200, true), // 失敗
mockFetch("comments", 600),
]);
const ok = results.filter(r => r.status === "fulfilled").map(r => r.value);
const ng = results.filter(r => r.status === "rejected").map(r => r.reason.message);
console.log("完成但成功的結果:", ok);
console.log("失敗原因:", ng);
}
// 跑看看
demoAllSuccess().then(() => demoAllWithFailure()).then(() => demoAllSettled());
一個常見的用法是「設定超時」。如果 API 太久沒回來,就用備援或直接放棄。
// 超時工具:若超過 ms 就 reject
function timeout(ms) {
return new Promise((_, reject) =>
setTimeout(() => reject(new Error(`超過 ${ms}ms,逾時`)), ms)
);
}
async function withTimeout() {
try {
// 真的 API(這裡用 mock)
const realRequest = mockFetch("慢吞吞 API", 2000);
// 跟 timeout 競速,誰先回來就採用誰
const res = await Promise.race([realRequest, timeout(1000)]);
console.log("API 回來了:", res);
} catch (e) {
console.error("RACE 逾時或失敗:", e.message);
}
}
withTimeout();
Promise.all
:適合「要嘛全有、要嘛全無」的情境;其中一個失敗就整組失敗。
Promise.race
:適合「要最先回來的結果」,包含最快失敗也會觸發。
try/catch
在 async
函式裡很好寫,讓錯誤處理清楚很多。
allSettled
雖然不在今天的清單,但在「想要看全部結果(成功/失敗)」時很好用。
自己做 delay()
和 mockFetch()
來練習,真的能更理解非同步的節奏。
今天最有感的是:同時發出多個請求、再決定要等誰或怎麼處理錯誤,這件事其實不難,關鍵是選對工具。
明天我會把這些東西帶到「模組化與 NPM」,把程式分檔、用套件,讓小專案更像樣。💪